/**
 * Copyright (c) 2007-2008 Nearchos Paspallis
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * AspectSense
 *
 * Middleware for annotation-based, context-aware and adaptive services for
 * mobile and ubiquitous computing
 *
 * This project is an open-source effort, initiated by Nearchos Paspallis as
 * part of his PhD work at the University of Cyprus (http://www.ucy.ac.cy).
 *
 * More information about the project is available at "http://aspectsense.org".
 */

package org.aspectsense.evaluators;

import org.aspectsense.evaluators.conditional.*;
import org.aspectsense.evaluators.functional.*;
import org.aspectsense.evaluators.special.ContextEvaluator;
import org.aspectsense.evaluators.special.ServiceUtilityEvaluator;
import org.aspectsense.coreservices.ContextService;

import java.util.Map;

/**
 * @author Nearchos Paspallis [nearchos@cs.ucy.ac.cy]
 *         Date: Jan 4, 2008
 *         Time: 11:56:13 AM
 */
public class DefaultParser implements Parser
{
    public static final String TRUE_STRING = "true";
    public static final String FALSE_STRING = "false";

    private final Evaluators evaluators;

    private final ContextService contextService;

    public DefaultParser(final Evaluators evaluators, final ContextService contextService)
    {
        if(evaluators == null || contextService == null)
        {
            throw new NullPointerException("Illegal null argument!");
        }

        this.evaluators = evaluators;
        this.contextService = contextService;
    }

    private String parseStringLiteral(final String literalS)
            throws ParserException
    {
        final char [] charArray = literalS.toCharArray();

        for(final char c : charArray)
        {
            if(!((c >= 'a' && c <= 'z')
                    || (c >= 'A' && c <= 'Z')
                    || (c >= '0' && c <= '9')
                    || c == '.'
                    || c == '-'
                    || c == '_'))
            {
                throw new ParserException("Illegal literal: " +  literalS + "; character not allowed: " + c);
            }
        }

        return literalS;
    }

    private double parseDoubleLiteral(final String literalS)
            throws ParserException
    {
        try
        {
            double dValue = Double.parseDouble(literalS);
            if(dValue >= 0d && dValue <= 1.0d)
            {
                return dValue;
            }
            throw new ParserException("The specified double literal value is "
                    + "outside the [0,1] boundaries: " + dValue);
        }
        catch (NumberFormatException nfe)
        {
            throw new ParserException("Invalid double literal value format: " + literalS);
        }
    }

    public UtilityEvaluator parseUtilityEvaluator(final Map<String,String> constants, final String utilityFunctionS)
            throws ParserException
    {
        final int indexOfStar = utilityFunctionS.indexOf('*');
        final int indexOfSlash = utilityFunctionS.indexOf('/');
        final int indexOfPlus = utilityFunctionS.indexOf('+');
//        final int indexOfMinus = utilityFunctionS.indexOf('-');
        final int indexOfQuestionMark = utilityFunctionS.indexOf('?');

        if(indexOfStar != -1)
        {
            final String leftUtilityFunctionS = utilityFunctionS.substring(0, indexOfStar).trim();
            final String rightUtilityFunctionS = utilityFunctionS.substring(indexOfStar + 1).trim();

            final UtilityEvaluator leftUtilityEvaluator = parseUtilityEvaluator(constants, leftUtilityFunctionS);
            final UtilityEvaluator rightUtilityEvaluator = parseUtilityEvaluator(constants, rightUtilityFunctionS);

            return new ProductEvaluator(leftUtilityEvaluator, rightUtilityEvaluator);
        }
        else if(indexOfSlash != -1)
        {
            final String leftUtilityFunctionS = utilityFunctionS.substring(0, indexOfStar).trim();
            final String rightUtilityFunctionS = utilityFunctionS.substring(indexOfStar + 1).trim();

            final UtilityEvaluator leftUtilityEvaluator = parseUtilityEvaluator(constants, leftUtilityFunctionS);
            final UtilityEvaluator rightUtilityEvaluator = parseUtilityEvaluator(constants, rightUtilityFunctionS);

            return new DivisionEvaluator(leftUtilityEvaluator, rightUtilityEvaluator);
        }
        else if(indexOfPlus != -1)
        {
            final String leftUtilityFunctionS = utilityFunctionS.substring(0, indexOfPlus).trim();
            final String rightUtilityFunctionS = utilityFunctionS.substring(indexOfPlus + 1).trim();

            final UtilityEvaluator leftUtilityEvaluator = parseUtilityEvaluator(constants, leftUtilityFunctionS);
            final UtilityEvaluator rightUtilityEvaluator = parseUtilityEvaluator(constants, rightUtilityFunctionS);

            return new SumEvaluator(leftUtilityEvaluator, rightUtilityEvaluator);
        }
/*
        else if(indexOfMinus != -1)
        {
            final String leftUtilityFunctionS = utilityFunctionS.substring(0, indexOfPlus).trim();
            final String rightUtilityFunctionS = utilityFunctionS.substring(indexOfPlus + 1).trim();

            final UtilityEvaluator leftUtilityEvaluator = parseUtilityEvaluator(constants, leftUtilityFunctionS);
            final UtilityEvaluator rightUtilityEvaluator = parseUtilityEvaluator(constants, rightUtilityFunctionS);

            return new SubstractionEvaluator(leftUtilityEvaluator, rightUtilityEvaluator);
        }
*/
        else if(indexOfQuestionMark != -1)
        {
            final int indexOfSemicolon = utilityFunctionS.indexOf(':');
            assert indexOfSemicolon != -1;

            final String conditionS = utilityFunctionS.substring(0, indexOfQuestionMark).trim();
            final String ifEvaluatorS = utilityFunctionS.substring(indexOfQuestionMark + 1, indexOfSemicolon).trim();
            final String elseEvaluatorS = utilityFunctionS.substring(indexOfSemicolon + 1).trim();

            final ConditionEvaluator conditionEvaluator = parseConditionEvaluator(constants, conditionS);
            final UtilityEvaluator ifUtilityEvaluator = parseUtilityEvaluator(constants, ifEvaluatorS);
            final UtilityEvaluator elseUtilityEvaluator = parseUtilityEvaluator(constants, elseEvaluatorS);

            return new QuestionMarkEvaluator(conditionEvaluator, ifUtilityEvaluator, elseUtilityEvaluator);
        }
        else // assume literal
        {
            return parseLiteral(utilityFunctionS);
        }
    }

    private ConditionEvaluator parseConditionEvaluator(final Map<String, String> constants, final String conditionS)
            throws ParserException
    {
        if(conditionS == null)
        {
            throw new NullPointerException("Illegal null argument");
        }

        final int indexOfLessThanOrEqual = conditionS.indexOf("<=");
        final int indexOfLessThan = conditionS.indexOf("<");
        final int indexOfGreaterThanOrEqual = conditionS.indexOf(">=");
        final int indexOfGreaterThan = conditionS.indexOf(">");
        final int indexOfEquality = conditionS.indexOf("==");
        final int indexOfInequality = conditionS.indexOf("!=");
        final int indexOfConjuction = conditionS.indexOf("&&");
        final int indexOfDisjuction = conditionS.indexOf("||");

        if(conditionS.startsWith("!"))
        {
            final String subConditionS = conditionS.substring(1);

            return new NegationEvaluator(parseConditionEvaluator(constants, subConditionS));
        }
        else if(indexOfLessThanOrEqual != -1)
        {
            final String lLiteral = conditionS.substring(0, indexOfLessThanOrEqual).trim();
            final String rLiteral = conditionS.substring(indexOfLessThanOrEqual + 1).trim();

            final UtilityEvaluator lUtilityEvaluator = parseUtilityEvaluator(constants, lLiteral);
            final UtilityEvaluator rUtilityEvaluator = parseUtilityEvaluator(constants, rLiteral);

            return new LessOrEqualThanEvaluator(lUtilityEvaluator, rUtilityEvaluator);
        }
        else if(indexOfLessThan != -1)
        {
            final String lLiteral = conditionS.substring(0, indexOfLessThan).trim();
            final String rLiteral = conditionS.substring(indexOfLessThan + 1).trim();

            final UtilityEvaluator lUtilityEvaluator = parseUtilityEvaluator(constants, lLiteral);
            final UtilityEvaluator rUtilityEvaluator = parseUtilityEvaluator(constants, rLiteral);

            return new LessThanEvaluator(lUtilityEvaluator, rUtilityEvaluator);
        }
        else if(indexOfGreaterThanOrEqual != -1)
        {
            final String lLiteral = conditionS.substring(0, indexOfGreaterThanOrEqual).trim();
            final String rLiteral = conditionS.substring(indexOfGreaterThanOrEqual + 1).trim();

            final UtilityEvaluator lUtilityEvaluator = parseUtilityEvaluator(constants, lLiteral);
            final UtilityEvaluator rUtilityEvaluator = parseUtilityEvaluator(constants, rLiteral);

            return new GreaterOrEqualThanEvaluator(lUtilityEvaluator, rUtilityEvaluator);
        }
        else if(indexOfGreaterThan != -1)
        {
            final String lLiteral = conditionS.substring(0, indexOfGreaterThan).trim();
            final String rLiteral = conditionS.substring(indexOfGreaterThan + 1).trim();

            final UtilityEvaluator lUtilityEvaluator = parseUtilityEvaluator(constants, lLiteral);
            final UtilityEvaluator rUtilityEvaluator = parseUtilityEvaluator(constants, rLiteral);

            return new GreaterThanEvaluator(lUtilityEvaluator, rUtilityEvaluator);
        }
        else if(indexOfEquality != -1)
        {
            final String lLiteral = conditionS.substring(0, indexOfEquality).trim();
            final String rLiteral = conditionS.substring(indexOfEquality + 1).trim();

            final UtilityEvaluator lUtilityEvaluator = parseUtilityEvaluator(constants, lLiteral);
            final UtilityEvaluator rUtilityEvaluator = parseUtilityEvaluator(constants, rLiteral);

            return new EqualityEvaluator(lUtilityEvaluator, rUtilityEvaluator);
        }
        else if(indexOfInequality != -1)
        {
            final String lLiteral = conditionS.substring(0, indexOfInequality).trim();
            final String rLiteral = conditionS.substring(indexOfInequality + 1).trim();

            final UtilityEvaluator lUtilityEvaluator = parseUtilityEvaluator(constants, lLiteral);
            final UtilityEvaluator rUtilityEvaluator = parseUtilityEvaluator(constants, rLiteral);

            return new InequalityEvaluator(lUtilityEvaluator, rUtilityEvaluator);
        }
        else if(indexOfConjuction != -1)
        {
            final String lLiteral = conditionS.substring(0, indexOfConjuction).trim();
            final String rLiteral = conditionS.substring(indexOfConjuction + 1).trim();

            final ConditionEvaluator lConditionEvaluator = parseConditionEvaluator(constants, lLiteral);
            final ConditionEvaluator rConditionEvaluator = parseConditionEvaluator(constants, rLiteral);

            return new ConjunctionEvaluator(lConditionEvaluator, rConditionEvaluator);
        }
        else if(indexOfDisjuction != -1)
        {
            final String lLiteral = conditionS.substring(0, indexOfDisjuction).trim();
            final String rLiteral = conditionS.substring(indexOfDisjuction + 1).trim();

            final ConditionEvaluator lConditionEvaluator = parseConditionEvaluator(constants, lLiteral);
            final ConditionEvaluator rConditionEvaluator = parseConditionEvaluator(constants, rLiteral);

            return new DisjunctionEvaluator(lConditionEvaluator, rConditionEvaluator);
        }
        else if(conditionS.startsWith("context["))
        {
            final String contextLiteralS = conditionS.substring(8, conditionS.length() - 1).trim();

            return new ContextEvaluator(contextService, contextLiteralS);
        }
        else
        {
            throw new ParserException("Invalid condition literal: " + conditionS);
        }
    }

    private UtilityEvaluator parseLiteral(final String literalS)
            throws ParserException
    {
        if(literalS == null || literalS.isEmpty())
        {
            throw new ParserException("Illegal null or empty literal!");
        }

        if(literalS.startsWith("context["))
        {
            final String contextID = literalS.substring(8, literalS.length() - 1).trim();
            final String literalContextID = parseStringLiteral(contextID);

            return new ContextEvaluator(contextService, literalContextID);
        }
        else if(literalS.startsWith("utility["))
        {
            final String serviceID = literalS.substring(8, literalS.length() - 1).trim();
            final String literalServiceID = parseStringLiteral(serviceID);

            return new ServiceUtilityEvaluator(evaluators, literalServiceID);
        }
        else // assume a constant literal (double)
        {
            final double constant = parseDoubleLiteral(literalS);

            return new DoubleConstantEvaluator(constant);
        }
    }

}